#include "SpringScrollViewNode.h"
#include "RNOH/arkui/NativeNodeApi.h"
#include <arkui/ui_input_event.h>
#include <bits/alltypes.h>


static constexpr ArkUI_NodeEventType SCROLL_NODE_EVENT_TYPES[] = {NODE_SCROLL_EVENT_ON_SCROLL,
                                                                  NODE_SCROLL_EVENT_ON_SCROLL_START,
                                                                  NODE_SCROLL_EVENT_ON_SCROLL_STOP,
                                                                  NODE_SCROLL_EVENT_ON_SCROLL_FRAME_BEGIN,
                                                                  NODE_SCROLL_EVENT_ON_SCROLL_EDGE,
                                                                  NODE_TOUCH_EVENT,
                                                                  NODE_EVENT_ON_APPEAR};

namespace rnoh {

    SpringScrollViewNode::SpringScrollViewNode()
        : ArkUINode(NativeNodeApi::getInstance()->createNode(ArkUI_NodeType::ARKUI_NODE_COLUMN)),
          m_headerArkUINodeHandle(nullptr), m_scrollArkUINodeHandle(nullptr), m_footerArkUINodeHandle(nullptr) {

        ArkUI_NumberValue columnFlexValue[] = {{.i32 = ARKUI_FLEX_ALIGNMENT_CENTER}};
        ArkUI_AttributeItem columnFlexItem = {columnFlexValue, sizeof(columnFlexValue) / sizeof(ArkUI_NumberValue)};
        NativeNodeApi::getInstance()->setAttribute(m_nodeHandle, NODE_COLUMN_JUSTIFY_CONTENT, &columnFlexItem);

        for (auto eventType : SCROLL_NODE_EVENT_TYPES) {
            maybeThrow(NativeNodeApi::getInstance()->registerNodeEvent(m_nodeHandle, eventType, eventType, nullptr));
        }
    }
    SpringScrollViewNode::~SpringScrollViewNode() {}
    void
    SpringScrollViewNode::setSpringScrollViewNodeDelegate(SpringScrollViewNodeDelegate *springScrollViewNodeDelegate) {
        m_scrollNodeDelegate = springScrollViewNodeDelegate;
        for (auto eventType : SCROLL_NODE_EVENT_TYPES) {
            NativeNodeApi::getInstance()->unregisterNodeEvent(m_nodeHandle, eventType);
        }
    }

    void SpringScrollViewNode::insertChild(ArkUINode &child, std::size_t index) {
        maybeThrow(NativeNodeApi::getInstance()->addChild(m_nodeHandle, child.getArkUINodeHandle()));
    }

    void SpringScrollViewNode::removeChild(ArkUINode &child) {
        maybeThrow(NativeNodeApi::getInstance()->removeChild(m_nodeHandle, child.getArkUINodeHandle()));
    }

    void SpringScrollViewNode::onNodeEvent(ArkUI_NodeEventType eventType, EventArgs &eventArgs) {
        LOG(INFO) << "[clx] <SpringScrollViewNode::onNodeEvent> in!";
        ArkUI_NodeEvent *event = nullptr;
        ArkUI_UIInputEvent *inputEvent = OH_ArkUI_NodeEvent_GetInputEvent(event);
        if (eventType == ArkUI_NodeEventType::NODE_SCROLL_EVENT_ON_SCROLL_EDGE) {
            switch (eventArgs[0].i32) {
            case ARKUI_SCROLL_EDGE_TOP:
            case ARKUI_SCROLL_EDGE_BOTTOM:
                onVerticalAnimationEnd();
                break;
            case ARKUI_SCROLL_EDGE_START:
            case ARKUI_SCROLL_EDGE_END:
                onHorizontalAnimationEnd();
                break;
            }
        } else if (eventType == NODE_EVENT_ON_APPEAR) {
            m_scrollNodeDelegate->onScroll();
        } else if (eventType == NODE_SCROLL_EVENT_ON_SCROLL_STOP) {
            m_scrollNodeDelegate->onLoading();

        } else if (eventType == ArkUI_NodeEventType::NODE_TOUCH_EVENT) {
            switch (eventArgs[0].i32) {
            case UI_TOUCH_EVENT_ACTION_DOWN:
                onDown(inputEvent);
                break;
            case UI_TOUCH_EVENT_ACTION_MOVE:
                onMove(inputEvent);
                break;
            case UI_TOUCH_EVENT_ACTION_UP:
                onUp(inputEvent);
                break;
            case UI_TOUCH_EVENT_ACTION_CANCEL:
                beginPoint.x = beginPoint.y = 0;
                if (!dragging)
                    // events.sendEvent("onCustomTouchEnd");
                    break;
            }
            return;
        }
    }

    void SpringScrollViewNode::onMove(ArkUI_UIInputEvent *evt) {
        if (!this->scrollEnabled) {
            return;
        }
        int rawX = arkUI_IntOffset->x;
        int rawY = arkUI_IntOffset->y;
        if (inverted) {
            drag(rawX - lastPoint.x, rawY - lastPoint.y);
        } else {
            drag(lastPoint.x - rawX, lastPoint.y - rawY);
        }
        lastPoint.x = rawX;
        lastPoint.y = rawY;
    }
    void SpringScrollViewNode::onDown(ArkUI_UIInputEvent *evt) {

        beginPoint.x = lastPoint.x = arkUI_IntOffset->x;
        beginPoint.y = lastPoint.y = arkUI_IntOffset->y;
        //         if (cancelAllAnimations()) {
        //             dragging = true;
        //         }
    }
    void SpringScrollViewNode::drag(float x, float y) {
        y = this->getYDampingCoefficient() * y;
        x = this->getXDampingCoefficient() * x;
        if (directionalLockEnabled) {
            if (draggingDirection == "") {
                if (std::abs(x) > std::abs(y)) {
                    draggingDirection = "h";
                } else {
                    draggingDirection = "v";
                }
            }
            if (draggingDirection == "h")
                y = 0;
            if (draggingDirection == "v")
                x = 0;
        }
        moveToOffset(contentOffset.x + x, contentOffset.y + y);
    }

    void SpringScrollViewNode::moveToOffset(float x, float y) {
        if (!scrollEnabled)
            return;
        if (!bounces) {
            if (y < -contentInsets.top)
                y = -contentInsets.top;
            if (y > contentSize.height - size.height + contentInsets.bottom)
                y = contentSize.height - size.height + contentInsets.bottom;
        }
        if (contentSize.width <= size.width || !bounces) {
            if (x < -contentInsets.left)
                x = -contentInsets.left;
            if (x > contentSize.width - size.width + contentInsets.right)
                x = contentSize.width - size.width + contentInsets.right;
        }
        if (contentOffset.y == y && contentOffset.x == x)
            return;
        if (shouldPulling()) {
            refreshStatus = "pulling";
        } else if (shouldPullingEnough()) {
            refreshStatus = "pullingEnough";
        } else if (shouldRefresh()) {
            refreshStatus = "refreshing";
            contentInsets.top = refreshHeaderHeight;
        } else if (shouldPullingCancel()) {
            refreshStatus = "pullingCancel";
        } else if (shouldWaiting()) {
            refreshStatus = "waiting";
        }
        if (shouldDragging()) {
            loadingStatus = "dragging";
        } else if (shouldDraggingEnough()) {
            loadingStatus = "draggingEnough";
        } else if (shouldDraggingCancel()) {
            loadingStatus = "draggingCancel";
        } else if (shouldFooterWaiting()) {
            loadingStatus = "waiting";
        }
        setContentOffset(x, y);
    }

    float SpringScrollViewNode::getPageWidth() { return pageSize.width <= 0 ? size.width : pageSize.width; }

    float SpringScrollViewNode::getPageHeight() { return pageSize.height <= 0 ? size.height : pageSize.height; }

    bool SpringScrollViewNode::shouldPulling() {
        return refreshHeaderHeight > 0 && overshootHead() &&
               (refreshStatus == "waiting" || refreshStatus == "pullingCancel");
    }

    bool SpringScrollViewNode::shouldPullingEnough() {
        return refreshHeaderHeight > 0 && overshootRefresh() && refreshStatus == "pulling";
    }

    bool SpringScrollViewNode::shouldRefresh() {
        return !dragging && refreshHeaderHeight > 0 && overshootRefresh() && refreshStatus == "pullingEnough";
    }

    bool SpringScrollViewNode::shouldPullingCancel() {
        return refreshHeaderHeight > 0 && refreshStatus == "pullingEnough" && overshootHead() && !overshootRefresh();
    }

    bool SpringScrollViewNode::shouldWaiting() {
        return refreshHeaderHeight > 0 && !overshootHead() &&
               (refreshStatus == "rebound" || refreshStatus == "pullingCancel");
    }

    bool SpringScrollViewNode::shouldDragging() {
        return loadingFooterHeight > 0 && overshootFooter() &&
               (loadingStatus == "waiting" || loadingStatus == "draggingCancel");
    }

    bool SpringScrollViewNode::shouldDraggingEnough() {
        return loadingFooterHeight > 0 && overshootLoading() && loadingStatus == "dragging";
    }

    bool SpringScrollViewNode::shouldLoad() {
        return loadingFooterHeight > 0 && overshootLoading() && loadingStatus == "draggingEnough";
    }

    bool SpringScrollViewNode::shouldDraggingCancel() {
        return loadingFooterHeight > 0 && loadingStatus == "draggingEnough" && overshootFooter() && !overshootLoading();
    }

    bool SpringScrollViewNode::shouldFooterWaiting() {
        return loadingFooterHeight > 0 && !overshootFooter() &&
               (loadingStatus == ("rebound") || loadingStatus == ("draggingCancel"));
    }
    bool SpringScrollViewNode::canHorizontalScroll() { return scrollEnabled && contentSize.width > size.width; }
    bool SpringScrollViewNode::overshootVertical() { return overshootHead() || overshootFooter(); }

    bool SpringScrollViewNode::overshootHead() { return contentOffset.y < -contentInsets.top; }

    bool SpringScrollViewNode::overshootFooter() { return contentOffset.y > contentSize.height - size.height; }

    bool SpringScrollViewNode::overshootLoading() {
        return contentOffset.y > -size.height + contentSize.height + loadingFooterHeight;
    }

    bool SpringScrollViewNode::overshootRefresh() { return contentOffset.y < -contentInsets.top - refreshHeaderHeight; }

    bool SpringScrollViewNode::overshootLeft() { return contentOffset.x < -contentInsets.left; }

    bool SpringScrollViewNode::overshootRight() {
        return contentOffset.x > contentInsets.right + contentSize.width - size.width;
    }
    bool SpringScrollViewNode::overshootHorizontal() { return overshootLeft() || overshootRight(); }
    float SpringScrollViewNode::getYDampingCoefficient() { return overshootVertical() ? 0.5f : 1.0f; }

    float SpringScrollViewNode::getXDampingCoefficient() { return overshootLeft() || overshootRight() ? 0.5f : 1.0f; }

    void SpringScrollViewNode ::setContentOffset(float x, float y) {
        if (this->contentOffset.x == x && this->contentOffset.x == y) {
            return;
        }
        this->contentOffset.x = x;
        this->contentOffset.y = y;
        ArkUI_NumberValue indexValue[] = {{.f32 = this->contentOffset.x}, {.f32 = this->contentOffset.y}};
        ArkUI_AttributeItem indexItem = {indexValue, sizeof(indexValue) / sizeof(ArkUI_NumberValue)};
        LOG(WARNING) << "[clx] <SpringScrollViewNode::setContentOffset>" << &indexItem;
        maybeThrow(NativeNodeApi::getInstance()->setAttribute(m_nodeHandle, NODE_SCROLL_OFFSET, &indexItem));
    }

    void SpringScrollViewNode ::setLoadingFooterHeight(float height) { loadingFooterHeight = height; }
    void SpringScrollViewNode ::setAllLoaded(bool allLoaded) {
        loadingStatus = allLoaded ? "allLoaded" : "waiting";
        if (allLoaded) {
            contentInsets.bottom = 0;
        }
    }

    void SpringScrollViewNode ::setDecelerationRate(float rate) { decelerationRate = rate; }

    void SpringScrollViewNode ::setInitialContentOffset(float x, float y) {
        initialContentOffset.x = x;
        initialContentOffset.y = y;
        ArkUI_NumberValue indexValue[] = {{.f32 = this->initialContentOffset.x}, {.f32 = this->initialContentOffset.y}};
        ArkUI_AttributeItem indexItem = {indexValue, sizeof(indexValue) / sizeof(ArkUI_NumberValue)};
        LOG(WARNING) << "[clx] <SpringScrollViewNode::setInitialContentOffset>" << &indexItem;
        maybeThrow(NativeNodeApi::getInstance()->setAttribute(m_nodeHandle, NODE_SCROLL_OFFSET, &indexItem));
    }

    void SpringScrollViewNode ::setPageSize(float width, float height) {
        this->pageSize.width = width;
        this->pageSize.height = height;

        if (this->pageSize.width == width && this->pageSize.height == height) {
            return;
        }
        this->pageSize.width = width;
        this->pageSize.height = height;
        ArkUI_NumberValue indexValue[] = {{.f32 = this->pageSize.width}, {.f32 = this->pageSize.height}};
        ArkUI_AttributeItem indexItem = {indexValue, sizeof(indexValue) / sizeof(ArkUI_NumberValue)};
        LOG(WARNING) << "[clx] <SpringScrollViewNode::setPageSize>" << &indexItem;
        maybeThrow(NativeNodeApi::getInstance()->setAttribute(m_nodeHandle, NODE_SCROLL_SNAP, &indexItem));
    }

    void SpringScrollViewNode ::setRefreshHeaderHeight(float v) { this->refreshHeaderHeight = v; }

    void SpringScrollViewNode ::setBounces(bool v) {
        if (this->bounces == v) {
            return;
        }
        this->bounces = v;
    }

    void SpringScrollViewNode ::setScrollEnabled(bool v) {
        if (this->scrollEnabled == v) {
            return;
        }
        this->scrollEnabled = v;
    }

    void SpringScrollViewNode ::setInverted(bool v) { this->inverted = v; }

    void SpringScrollViewNode ::setDirectionalLockEnabled(bool v) {
        if (this->directionalLockEnabled == v) {
            return;
        }
        this->directionalLockEnabled = v;
    }

    void SpringScrollViewNode ::setPagingEnabled(bool v) {
        if (this->pagingEnabled == v) {
            return;
        }
        this->pagingEnabled = v;
    }

//     void SpringScrollViewNode ::receiveCommand(SpringScrollViewNode &scrollView, const std::string &commandId,
//                                                const std::vector<bool> &args) {
//         if (commandId == "10000") {
//             scrollView.endRefresh();
//         } else if (commandId == "10001" && !args.empty()) {
//             scrollView.endLoading(args[0]);
//         } else if (commandId == "10002" && args.size() == 3) {
//             scrollView.scrollTo(args[0], args[1], args[2]);
//         } else {
//             LOG(WARNING) << "Invalid command or arguments";
//         }
//     }
} // namespace rnoh
